﻿namespace ScrumLite.Services
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Transactions;
    using System.Linq;
    using Contracts;
    using System.Globalization;

    public class LinqRequestsService : IRequestsService
    {
        ScrumLite.Data.IRequestStore store;

        public LinqRequestsService(ScrumLite.Data.IRequestStore store)
        {
            this.store = store;
        }

        public LinqRequestsService() : this(new ScrumLite.Data.RequestStore())
        {
        }

        public Request[] GetCommittedRequests(string projectId)
        {
            List<Request> requests = new List<ScrumLite.Data.Request>(
                    this.store.RetrieveCommittedRequests(new Guid(projectId))).
                ConvertAll<Request>(delegate(ScrumLite.Data.Request req)
            {
                return new Request()
                {
                    Id = req.id,
                    Moniker = req.moniker,
                    Notes = req.notes,
                    Priority = req.priority,
                    Status = req.en_status.HasValue ? (RequestStatus)req.en_status.Value : RequestStatus.Waiting,
                    Title = req.title,
                    Weight = req.weight,
                    Requestor = req.requestor,
                    Committed = req.committed,
                    CreatedOn = req.createdOn,
                    CompletedOn = req.completedOn
                };
            });

            return requests.ToArray();
        }

        public Request[] GetOpenedRequests(string projectId)
        {
            List<Request> requests = new List<ScrumLite.Data.Request>(
                    this.store.RetrieveOpenedRequests(new Guid(projectId))).
                ConvertAll<Request>(delegate(ScrumLite.Data.Request req)
                {
                    return new Request()
                    {
                        Id = req.id,
                        Moniker = req.moniker,
                        Notes = req.notes,
                        Priority = req.priority,
                        Status = req.en_status.HasValue ? (RequestStatus)req.en_status.Value : RequestStatus.Waiting,
                        Title = req.title,
                        Weight = req.weight,
                        Order = req.order,
                        Committed = req.committed,
                        Requestor = req.requestor,
                        DueOn = req.dueOn
                    };
                });

            return requests.ToArray();
        }

        public Collection<Request> GetRequests(string projectId, string workArea)
        {
            List<Request> requests = new List<ScrumLite.Data.Request>(
                    this.store.RetrieveRequests(new Guid(projectId), workArea)).
                ConvertAll<Request>(delegate(ScrumLite.Data.Request req)
                {
                    return new Request()
                    {
                        Id = req.id,
                        Moniker = req.moniker,
                        Notes = req.notes,
                        Priority = req.priority,
                        Status = req.en_status.HasValue ? (RequestStatus)req.en_status.Value : RequestStatus.Waiting,
                        Title = req.title,
                        Weight = req.weight,
                        Order = req.order,
                        Committed = req.committed,
                        DueOn = req.dueOn,
                        Requestor = req.requestor, //TODO: Esto no se estaba devolviendo originalmente
                        CreatedOn = req.createdOn, //TODO: Esto no se estaba devolviendo originalmente
                        CompletedOn = req.completedOn //TODO: Esto no se estaba devolviendo originalmente
                    };
                });

            return new Collection<Request>(requests);
        }

        public WorkAreaWeight[] GetWorkAreasGroupByWeight(string projectId)
        {
            Dictionary<Guid, ScrumLite.Data.WorkAreaReport> pairs = this.store.RetrieveWorkAreasGroupedByWeight(new Guid(projectId));
            List<WorkAreaWeight> wa = new List<WorkAreaWeight>();
            foreach (KeyValuePair<Guid,ScrumLite.Data.WorkAreaReport> pair in pairs)
            {
                wa.Add(new WorkAreaWeight()
                {
                    Id = pair.Key,
                    Weight = Convert.ToInt32(pair.Value.TotalWeight, CultureInfo.InvariantCulture),
                    Name = pair.Value.Name,
                    Deliverables = Convert.ToInt32(pair.Value.DeliverableCount),
                    Requests = Convert.ToInt32(pair.Value.RequestCount, CultureInfo.InvariantCulture)
                });
            }
            return wa.ToArray();
        }
      
        public void SetPriorities(Guid[] requestIds)
        {
            using (TransactionScope scope = new TransactionScope())
            {
                this.store.UpdateRequestsPriority(requestIds);
                scope.Complete();
            }
        }

        public void SaveRequests(string projectId, Request[] requests)
        {
            using (TransactionScope scope = new TransactionScope())
            {
                foreach (Request request in requests)
                {
                    ScrumLite.Data.Request r = new ScrumLite.Data.Request()
                    {
                        id = request.Id,
                        fk_project = new Guid(projectId),
                        moniker = request.Moniker,
                        en_status = (byte?)request.Status,
                        notes = request.Notes,
                        priority = (byte?)request.Priority,
                        title = request.Title,
                        weight = (byte?)request.Weight,
                        committed = request.Committed,
                        requestor = request.Requestor,
                        dueOn = request.DueOn,
                        RequestWorkAreas = null
                    };
                    this.store.Update(r, false);
                }
                scope.Complete();
            }
        }      

        public int AddRequest(string projectId, Request request)
        {
            int moniker = 0;
            using (TransactionScope scope = new TransactionScope())
            {
                ScrumLite.Data.Request data = Map(request);
                data.moniker = GetNextMoniker(projectId);
                data.fk_project = new Guid(projectId);

                this.store.Add(data);
                scope.Complete();
                moniker = data.moniker;
            }
            return moniker;
        }

        public void UpdateRequest(string projectId, Request request)
        {
            ScrumLite.Data.Request data = Map(request);
            data.fk_project = new Guid(projectId);
            using (TransactionScope scope = new TransactionScope())
            {
                if (request.Status == RequestStatus.Completed)
                {
                    data.order = GetNextOrder(projectId);                   
                }

                this.store.Update(data, true);
                scope.Complete();
            }
        }

        private static ScrumLite.Data.Request Map(Request modelRequest)
        {
            if (modelRequest == null)
                return null;

            ScrumLite.Data.Request dataRequest = new ScrumLite.Data.Request()
            {
                id = modelRequest.Id,
                committed = modelRequest.Committed,
                moniker = modelRequest.Moniker,
                notes = modelRequest.Notes,
                order = modelRequest.Order,
                priority = (byte?)modelRequest.Priority,
                en_status = (byte?)modelRequest.Status,
                title = modelRequest.Title,
                weight = (byte?)modelRequest.Weight,
                createdOn = DateTime.Now,
                requestor = modelRequest.Requestor,
                dueOn = modelRequest.DueOn
            };

            foreach (WorkArea item in modelRequest.WorkAreas)
            {
                dataRequest.RequestWorkAreas.Add(new ScrumLite.Data.RequestWorkArea() { id = Guid.NewGuid(), fk_workarea = new Guid(item.Id), fk_request = modelRequest.Id });
            }

            return dataRequest;
        }

        public Dictionary<string, string> GetStatusList()
        {
            IList<ScrumLite.Data.RequestStatus> requestStatusLista = this.store.RetrieveRequestStatusList();

            Dictionary<string, string> statusList = new Dictionary<string, string>();
            foreach (ScrumLite.Data.RequestStatus  item in requestStatusLista )
            {
                statusList.Add(item.key.ToString(CultureInfo.InvariantCulture), item.value);
            }

            return statusList;
        }

        public void DeleteRequest(Guid requestId)
        {
            this.store.Delete(requestId);
        }

        public Request GetRequest(string projectId, int moniker)
        {
            ScrumLite.Data.Request req = this.store.RetrieveRequest(projectId, moniker);

            Request modelReq = new Request()
            {
                Id = req.id,
                Committed = req.committed,
                Moniker = req.moniker,
                Notes = req.notes,
                Order = req.order,
                Priority = req.priority,
                Requestor = req.requestor,
                Status = req.en_status.HasValue ? (RequestStatus)req.en_status.Value : RequestStatus.Waiting,
                Title = req.title,
                Weight = req.weight,
                CreatedOn = req.createdOn,
                CompletedOn = req.completedOn,
                DueOn = req.dueOn
            };

            return modelReq;
        }        

        public WorkArea[] GetWorkAreas(string projectId)
        {
            List<WorkArea> areas = (from wa in this.store.RetrieveWorkAreas(new Guid(projectId))
                                         select new WorkArea { Id = wa.id.ToString(), Name = wa.name }).ToList();

            return areas.ToArray();
        }

        public WorkArea[] GetWorkAreasByMoniker(string projectId, int moniker)
        {
            List<WorkArea> areas = (from wa in this.store.RetrieveWorkAreas(new Guid(projectId), moniker)
                                  select new WorkArea { Id = wa.id.ToString(), Name = wa.name }).ToList();

            return areas.ToArray();
        }

        public void AssignWorkAreas(string projectId, int moniker, string[] workAreaIds)
        {
            this.store.AssignWorkAreas(new Guid(projectId), moniker, workAreaIds);
        }

        private int GetNextMoniker(string projectUri)
        {
            int maxMoniker = this.store.RetrieveLastMoniker(new Guid(projectUri));
            return ++maxMoniker;
        }

        private int GetNextOrder(string projectId)
        {
            int maxOrder = this.store.RetrieveLastOrder(new Guid(projectId));
            return ++maxOrder;
        }

        public WorkArea GetWorkArea(string projectId, Guid id)
        {
            WorkArea area = (from wa in this.store.RetrieveWorkAreas(new Guid(projectId))
                                    where wa.id == id
                                    select new WorkArea { Id = wa.id.ToString(), Name = wa.name }).SingleOrDefault();

            return area;
        }

        public void SaveWorkArea(WorkArea wa, Guid projectId)
        {
            ScrumLite.Data.WorkArea workArea = new ScrumLite.Data.WorkArea() 
            {
                 fk_project = projectId,
                 id = new Guid(wa.Id),
                 name = wa.Name 
            };
            this.store.SaveWorkArea(workArea);
        }

        public Dictionary<int, int> GetWeeklyWeight(string projectId)
        {
            var weeklyWeight =
                from r in this.store.RetrieveCompletedRequests(new Guid(projectId))
                where r.weight.HasValue
                group r by GetYearWeek(r.completedOn) into g
                orderby g.Key
                select new { Week = g.Key, TotalWeight = g.Sum(r => r.weight.Value) }; 

            return weeklyWeight.ToDictionary(k => k.Week, v => v.TotalWeight);
        }

        public Dictionary<DayOfWeek, int> GetCommittedDailyWeight(string projectId)
        {            
            var dailyWeight =
                from r in this.store.RetrieveCommittedCompletedRequests(new Guid(projectId))
                where r.weight.HasValue
                where r.completedOn.HasValue
                group r by r.completedOn.Value.DayOfWeek into g
                orderby g.Key
                select new { Day = g.Key, TotalWeight = g.Sum(r => r.weight.Value) };

            return dailyWeight.ToDictionary(k => k.Day, v => v.TotalWeight);
        }

        public Dictionary<WorkArea, Dictionary<RequestStatus, int>> GetRequestsByStatusByWorkArea(string projectId)
        {
            Dictionary<ScrumLite.Data.WorkArea,Dictionary<byte?,int>> result= this.store.RetrieveRequestsByStatusByWorkArea(new Guid(projectId));
            return result.ToDictionary(wa => new WorkArea { Id = wa.Key.id.ToString(), Name = wa.Key.name },
                vReq => vReq.Value.ToDictionary(st => (RequestStatus)st.Key.Value,
                    req => req.Value));
        }

        public Dictionary<RequestStatus, int> GetRequestsByStatus(string projectId)
        {
            Dictionary<RequestStatus, int> result = new Dictionary<RequestStatus, int>();
            Dictionary<byte?, int> statusRequestsCount = this.store.RetrieveCountRequestsByStatus(new Guid(projectId));
            foreach (RequestStatus status in statusRequestsCount.Keys)
            {
                result[status] = statusRequestsCount[(Nullable<byte>)status];
            }
            return result;            
        }

        private static int GetYearWeek(DateTime? date)
        {
            CultureInfo invariant = CultureInfo.InvariantCulture;
            return invariant.Calendar.GetWeekOfYear(date.Value,
                                   invariant.DateTimeFormat.CalendarWeekRule,
                                   invariant.DateTimeFormat.FirstDayOfWeek);
        }

        public void CleanCommitted(string projectId, RequestStatus[] statusToUpdateCommitted)
        {
            List<byte> statusList = new List<byte>();

            foreach(RequestStatus r in statusToUpdateCommitted)
            {
                statusList.Add((byte)r);
            }
            this.store.CleanCommittedRequestByStatus(new Guid(projectId),statusList.ToArray());
        }

        public int GetRequestsTotalWeight(string projectId)
        {
            if (!String.IsNullOrEmpty(projectId))
            {
                return this.store.RetrieveRequestsTotalWeight(new Guid(projectId));
            }
            else
            {
                throw new ArgumentNullException("projectId");
            }
        }

        public int GetCommittedRequestsTotalWeight(string projectId)
        {
            if (!String.IsNullOrEmpty(projectId))
            {
                return this.store.RetrieveCommittedRequestsTotalWeight(new Guid(projectId));
            }
            else
            {
                throw new ArgumentNullException("projectId");
            }
        }
    }
}
